/**
 * 
 */
package xiaojian.toolkit.bean.tlv.decode.decoders;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.tlv.annotation.TlvType;
import xiaojian.toolkit.bean.tlv.decode.TLVDecodeContext;
import xiaojian.toolkit.bean.tlv.decode.TLVDecodeContextFactory;
import xiaojian.toolkit.bean.tlv.decode.TLVDecoder;
import xiaojian.toolkit.util.ByteUtils;


/**
 * @author marvin.ma
 *
 */
public class ObjectArrayTLVDecoder implements TLVDecoder {

	private static final Logger LOG = 
			LoggerFactory.getLogger(ObjectArrayTLVDecoder.class);
    
    private	TLVDecodeContextFactory	decodeContextFactory;
    
	public TLVDecodeContextFactory getDecodeContextFactory() {
		return decodeContextFactory;
	}

	public void setDecodeContextFactory(TLVDecodeContextFactory decodeContextFactory) {
		this.decodeContextFactory = decodeContextFactory;
	}
	
	public Object decode(final int tlvLength, final byte[] tlvValue, 
			final TLVDecodeContext ctx) {
		if ( !ctx.getValueType().isArray() ) {
			LOG.error("decode object's type {} is not array type", ctx.getValueType());
			return	null;
		}
		
		final Field field = ctx.getValueField();
        final TlvType param  = field.getAnnotation(TlvType.class);
        if ( -1 == param.elementTag() ) {
        	LOG.error("Array field[{}]'s elementTag not define, ignore", field);
        	return	null;
        }
		
		final Class<?> elementType = ctx.getValueType().getComponentType();
        final TLVDecoder decoder = ctx.getDecoderRepository().getDecoderOf(elementType);
        if ( null == decoder ) {
        	LOG.error("Array field[{}]/{} can not found decoder, ignore", 
        			field, elementType.getSimpleName());
        	return	null;
        }
        
        final List<Object> list = new ArrayList<Object>();
        
		int offset = 0;
		while ( offset + 8 <= tlvLength ) {
			
			final byte[] tagBytes = ArrayUtils.subarray(tlvValue, offset, offset + 4);
			final int elementTag = ctx.getNumberCodec().bytes2Int(tagBytes, 4);
			if ( elementTag != param.elementTag() ) {
	        	LOG.warn("real elementTag {} mismatch declare elemenetTag {}", 
	        			elementTag, param.elementTag() );
			}
			
			offset += 4;
			final byte[] lenBytes = ArrayUtils.subarray(tlvValue, offset, offset + 4);
			final int len = ctx.getNumberCodec().bytes2Int(lenBytes, 4);
			offset += 4;
			
			if ( len < 0 ) {
				LOG.error("elementTag {}'s len less than 0, bytes detail {}", elementTag,
						ByteUtils.bytesAsHexString(ArrayUtils.subarray(tlvValue, offset - 8, tlvValue.length ), 1024));
				throw new RuntimeException("elementTag [" + elementTag + "]'s len [" + len + "] less than 0");
			}
			
			final byte[] valueBytes = ArrayUtils.subarray(tlvValue, offset, offset + len);
			offset += len;

			final Object element = decoder.decode(len, valueBytes, 
					decodeContextFactory.createDecodeContext(elementType, field));
			if ( null != element ) {
				list.add(element);
			}
		}
		
		if ( !list.isEmpty() ) {
			//	build array objecct & fill all element
			final Object ret = Array.newInstance(elementType, list.size());
			for ( int idx = 0; idx < list.size(); idx++) {
				Array.set(ret, idx, list.get(idx));
			}
			return	ret;
		}
		else {
			return	null;
		}
	}

	public Class<?>[] getFieldType() {
		return new Class<?>[]{java.lang.Object[].class};
	}

}
